
"""
It provides the parser for exetuable files like <b>ELF</b> (Executable and
Linkable Format), <b>PE</b> (Portable & Executable) formats and <b>COFF</b>
(Common Object File Format) in Windows/DOS.

Both ELF and PE support 32-bit and 64-bit versions.
"""

import sys

__version__ = '0.30'

__extension__ = (
    ('Executable Files', ('*.exe', '*.dll')),
    ('Object Files', ('*.o', '*.obj', '*.so', '*.elf'))
)


def getMachine(nb, dat, **args):
    _MACHINE = {
        0x0000: 'UNKNOWN',
        0x014c: 'Intel 386',
        0x0160: 'MIPS big-endian',             0x0162: 'MIPS little-endian',
        0x0166: 'MIPS little-endian',          0x0168: 'MIPS little-endian',
        0x0169: 'MIPS little-endian WCE v2',
        0x0184: 'Alpha AXP',
        0x01a2: 'Hitachi SH3 little-endian',
        0x01a4: 'Hitachi SH3E/DSP little-endian',
        0x01a6: 'Hitachi SH4 little-endian',
        0x01a8: 'Hitachi SH5 little-endian',
        0x01c0: 'ARM little-endian',           0x01c2: 'Thumb',
        0x01c4: 'ARMv7 (or higher) Thumb mode only',
        0x01d3: 'Matsushita AM33',
        0x01f0: 'IBM PowerPC little-endian',
        0x01f1: 'IBM PowerPC with float point support',
        0x0200: 'Intel 64',
        0x0266: 'MIPS',                        0x0366: 'MIPS',
        0x0466: 'MIPS',
        0x0284: 'Alpha 64',
        0x0ebc: 'EFI byte code',
        0x8664: 'x64',
        0x9041: 'Mitsubishi M32 little-endian',
        0xaa64: 'ARMv8 in 64-bit mode',
    }

    ma = args['_i'].atoi(dat, args.get('e', True))
    if ma in _MACHINE:
        tp = '%04x - %s' % (ma, _MACHINE[ma])
    else:
        tp = 'unknown'

    return tp


def getOptionMagic(nb, dat, **args):
    _PE = {
        0x010b: 'PE',
        0x020b: 'PE+'
    }

    va = args['_i'].atoi(dat, args.get('e', True))

    if va in _PE:
        op = '%04x - %s' % (va, _PE[va])
    else:
        op = '%04x' % va

    return op


def getSubsystem(nb, dat, **args):
    tp = 'unknown'
    _SUB_SYSTEM = {
        1: 'native',           2: 'Windows GUI',
        3: 'Windows non-GUI',  5: 'OS/2',
        7: 'POSIX',            9: 'Windows CE',
        10: 'EFI Application',  11: 'EFI Driver with boot services',
        12: 'EFI driver with run-time services',
        13: 'EFI ROM',          14: 'XBOX'
    }

    sub = args['_i'].atoi(dat, args.get('e', True))
    if sub in _SUB_SYSTEM:
        tp = _SUB_SYSTEM[sub]

    return '%d (%s)' % (sub, tp)


def getVerString(nb, dat, **args):
    if len(dat) == 2:
        maj = ord(dat[0])
        edi = ord(dat[1])
    elif len(dat) == 4:
        maj = args['_i'].atoi(dat[0:2], args.get('e', True))
        edi = args['_i'].atoi(dat[2:4], args.get('e', True))

    return '%d.%d' % (maj, edi)


def getPhysicAddress(add, sections, **args):
    r = -1
    k, na, ofraw, szraw, vaddr = (None, None, None, None, None)

    for sect in sections:
        k, na, ofraw, szraw, vaddr = sect
        if vaddr <= add < vaddr + szraw:
            r = ofraw + add - vaddr
            break

    return r, na, ofraw, szraw, vaddr


def getSectionName(nb, dat, **args):
    nam = args['_i'].gets(dat[0:8])
    # = add the section address mapping
    vaddr = args['_i'].atoi(dat[12:16], args.get('e', True))
    szraw = args['_i'].atoi(dat[16:20], args.get('e', True))
    ofraw = args['_i'].atoi(dat[20:24], args.get('e', True))

    if szraw > 0:
        if 'sect' not in args:
            args['sect'] = list()
        args['sect'].append([nb & 0x0f, nam, ofraw, szraw, vaddr])

    return nam


def getDirectoryRvaString(nb, dat, **args):
    rva = args['_i'].atoi(dat[0:4], args.get('e', True))
    siz = args['_i'].atoi(dat[4:8], args.get('e', True))

    return 'rva: 0x%04x, size: %d' % (rva, siz)


def notifyProgress(args, info):
    if args.get('c') is not None:
        size = args.get('l') or 1
        args['c'](100 * args['start'] / size, info)


def checkSignature(source, target, pairs, args):
    notifyProgress(args, 'Read Signature')
    signature = target.readValue(source, len(pairs[1]), **args)
    return signature == pairs[0]


def readDosHeaderInfo(entry, source, target, args):
    notifyProgress(args, 'Read Header Infomation')

    # === DOS Header ===
    size = 2
    signature = target.readValue(source, size, **args)
    signature_str = target.readString(source, size, **args)
    obj = target.newItemObject('DOS HEADER')
    obj.appendChildren(
        target.newItemObject(
            'Sigature', '0x%04x (%s)' % (signature, signature_str),
            target.newFieldObject(args['start'], size)))

    args['offset'] += size
    # === Header Information ===
    _DOS_HEADER = (
        ('Bytes on Last Page', 2, 'decimal'),
        ('Total Pages in File', 2, 'decimal'),
        ('Relocation Items', 2, 'decimal'),
        ('Size of header in paragraphs', 2, 'decimal'),
        ('Minimum Extra Paragraphs', 2, 'decimal'),
        ('Maximum Extra Paragraphs', 2, 'decimal'),
        ('Initial Stack Segment', 2, 'decimal'),
        ('Initial Stack Pointer', 2, 'decimal'),
        ('Complemented Checksum', 2, 'decimal'),
        ('Initial Instruction Pointer', 2, 'decimal'),
        ('Initial Code Segment', 2, 'decimal'),
        ('Relocation Table Offset', 2, 'decimal'),
        ('Overlay Number ', 2, 'decimal')
    )

    ret, objectp, consumed, _, _ = target.parse(
        'Header Information', _DOS_HEADER, afile=source, **args)
    if ret:
        size += consumed
        args['offset'] += consumed
        obj.appendChildren(objectp)
        # === Extra Header Information ===
        _EXTRA_HEADER = (
            ('Reserved Word', 2, 'value', {'repeat': 4}),
            ('OEM Identifier', 2, 'value'),
            ('OEM Information', 2, 'value'),
            ('Reserved Word', 2, 'value', {'repeat': 10}),
            ('New header address', 4, 'value,export')
        )
        notifyProgress(args, 'Read Extra Header Infomation')
        ret, objectp, consumed, _, exported = target.parse(
            'Extra Header Information', _EXTRA_HEADER, afile=source, **args)

    if ret:
        size += consumed
        args['offset'] += consumed
        args['pe'] = exported[0]
        obj.appendChildren(objectp)

        obj.update(field=target.newFieldObject(
                   args['start'] + args['offset'] - size, size))

        entry.append(obj)

    return ret


def readPeCoffHeader(entry, source, target, args):
    notifyProgress(args, 'Read PE COFF Header')

    size = 0
    args['offset'] = args.get('pe', 0)

    obj = target.newItemObject('PE HEADER')
    if 'pe' in args:
        size = 4
        signature = target.readValue(source, size, **args)
        signature_str = target.readString(source, size, **args)
        obj.appendChildren(
            target.newItemObject(
                'Signature', '0x%04x (%s)' % (signature, signature_str),
                target.newFieldObject(args['start'] + args['offset'], size)))

        args['offset'] += size

    _PE_COFF_HEADER = (
        ('Machine', 2, 'string', {'function': getMachine}),
        ('Number of Section', 2, 'decimal,export'),
        ('Time Data Stamp', 4, 'timestamp'),
        ('Pointer To Symbol Table', 4, 'value'),
        ('Number of Symbols', 4, 'decimal'),
        ('Size of Optional header', 2, 'decimal,export'),
        ('Characteristics', 2, 'bits,update_content', {
            'template': (
                (1, 'bool', 'reloc_stripped'),
                (1, 'bool', 'execute'),
                (1, 'bool', 'line_num_stripped'),
                (1, 'bool', 'local_syms_stripped'),
                (1, 'bool', 'aggressive_ws_trim'),
                (1, 'bool', 'large_address_aware'),
                (1, 'bool', 'bytes_reversed_lo'),
                (1, 'bool', '32-bit-word_machine'),
                (1, 'bool', 'debug_stripped'),
                (1, 'bool', 'removable_run_from_swap'),
                (1, 'bool', 'net_run_from_swap'),
                (1, 'bool', 'file_system'),
                (1, 'bool', 'image_dll'),
                (1, 'bool', 'uniprocessor_only'),
                (1, 'bool', 'big_endian')
            )})
    )

    sizeopt = 0
    ret, objectp, consumed, _, exported = target.parse(
        'FILE HEADER', _PE_COFF_HEADER, afile=source, **args)
    if ret:
        size += consumed
        args['offset'] += consumed
        args['nbsect'], sizeopt = exported
        obj.appendChildren(objectp)

    nbrva = 0
    if ret and sizeopt > 0:
        _OPTIONAL_HEADER = (
            ('Magic', 2, 'string', {'function': getOptionMagic}),
            ('Linker Version', 2, 'recursive,string', {
                'function': getVerString,
                'subdescriptor': (
                    ('Major', 1, 'decimal'),
                    ('Minor', 1, 'decimal')
                )}),
            ('Size of Code', 4, 'decimal'),
            ('Size of Data', 4, 'decimal'),
            ('Size of bss', 4, 'decimal'),
            ('Address of Entry', 4, 'value'),
            ('Base of Code', 4, 'value'),
            ('Base of Data', 4, 'value'),
            ('Image Base', 4, 'value'),
            ('Section Alignment', 4, 'value'),
            ('File Alignment', 4, 'value'),
            ('OS Version', 4, 'recursive,string', {
                'function': getVerString,
                'subdescriptor': (
                    ('Major', 2, 'decimal'),
                    ('Minor', 2, 'decimal')
                )}),
            ('Image Version', 4, 'recursive,string', {
                'function': getVerString,
                'subdescriptor': (
                    ('Major', 2, 'decimal'),
                    ('Minor', 2, 'decimal')
                )}),
            ('Sub-system Version', 4, 'recursive,string', {
                'function': getVerString,
                'subdescriptor': (
                    ('Major', 2, 'decimal'),
                    ('Minor', 2, 'decimal')
                )}),
            ('Reserved1', 4, 'value'),
            ('Size of Iamge', 4, 'decimal'),
            ('Size of Header', 4, 'decimal'),
            ('Checksum', 4, 'value'),
            ('Subsystem', 2, 'string', {'function': getSubsystem}),
            ('DllCharacteristics', 2, 'bits,update_content', {
                'template': (
                    (6, 'ignore', '-'),
                    (1, 'bool', 'relocated'),
                    (1, 'bool', 'integrity'),
                    (1, 'bool', 'NX-compatible'),
                    (1, 'bool', 'not-isolation'),
                    (1, 'bool', 'non-SE'),
                    (1, 'bool', 'non-bind'),
                    (1, 'ignore', '-'),
                    (1, 'bool', 'WDM driver'),
                    (1, 'ignore', '-'),
                    (1, 'bool', 'terminal')
                )}),
            ('Size of Reserved Stack', 4, 'decimal'),
            ('Size of Commited Stack', 4, 'decimal'),
            ('Size of Reserved Heap', 4, 'decimal'),
            ('Size of Commited Stack', 4, 'decimal'),
            ('Loader Flag', 4, 'value'),
            ('Number of Rva', 4, 'decimal,export')
        )

        notifyProgress(args, 'Read Optional Header')
        ret, objectp, consumed, _, exported = target.parse(
            'OPTIONAL HEADER', _OPTIONAL_HEADER, afile=source, **args)
        if ret:
            size += consumed
            args['offset'] += consumed
            nbrva = exported[0]
            obj.appendChildren(objectp)

    if ret and nbrva > 0:
        notifyProgress(args, 'Read data directories for RVA')

        def rva(func):
            return {
                'function': func,
                'subdescriptor': (('Rva', 4, 'value,export'),
                                  ('Size', 4, 'decimal,export'))
            }

        _RVA_LIST = (
            ('Export Directory', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Import Directory', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Resource Directory', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Exception Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Security Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Base Relocation Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Debug Directory', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Architecture Specific Data', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Global Pointer', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('TLS Directory', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Load config Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Bound Import Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Import Address Table', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('Delay Import descriptor', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('COM Descriptor', 8, 'recursive,string',
             rva(getDirectoryRvaString)),
            ('unused', 8, 'recursive,string',
             rva(getDirectoryRvaString))
        )

        ret, objectp, consumed, _, exported = target.parse(
            'DATA DIRECTORY', _RVA_LIST[:nbrva], afile=source, **args)
        if ret:
            size += consumed
            args['offset'] += consumed
            obj.appendChildren(objectp)
            # hanlde the export RVA data
            rvad = dict()
            for k in range(len(exported) / 2):
                rvad[k] = exported[2 * k], exported[2 * k + 1]
            args['dirm'] = rvad

    if ret:
        obj.update(field=target.newFieldObject(
                   args['start'] + args['offset'] - size, size))
        entry.append(obj)

    return ret


def readSectionTable(entry, source, target, args):
    ret = False
    obj = list()

    x64 = args.get('x64', False)
    nbsect = args.get('nbsect', 0)
    _SECTION_HEADER = (
        ('Section', 40, 'recursive,string', {
            'func': getSectionName,
            'repeat': nbsect,
            'subdescriptor': (
                ('Name', 8, 'string,export'),
                ('VirtSize', 4, 'decimal'),
                ('VirtAddress', 4, 'decimal,export'),
                ('Raw dat size', 4, 'decimal,export'),
                ('Raw dat offs', 4, 'decimal,export'),
                ('Relocation offs', 4, 'decimal'),
                ('Line # offs', 4, 'decimal'),
                ('Locations', 2, 'decimal'),
                ('Line #', 2, 'decimal'),
                ('Characteristics', 4, 'value'),
            )}),
    )

    if nbsect > 0:
        notifyProgress(args, 'Read data directories for section table')
        ret, objectp, consumed, _, exported = target.parse(
            'SECTION TABLE', _SECTION_HEADER, afile=source, **args)
        if ret:
            args['offset'] += consumed
            entry.append(objectp)

            notifyProgress(args, 'Read raw data')

            start, end, sect = sys.maxint, 0, list()
            obj = target.newItemObject('RAW DATA')
            for k in range(len(exported) / 4):
                v = 4 * k
                rstart = args['start'] + exported[v + 3]
                rsize = exported[v + 2]
                obj.appendChildren(
                    target.newItemObject(
                        'RAW DATA #%d' % k,
                        field=target.newFieldObject(rstart, rsize)))

                if rstart < start:
                    start = rstart
                if rstart + rsize > end:
                    end = rstart + rsize

                sect.append((k, exported[v], exported[v + 3],
                            exported[v + 2], exported[v + 1]))

            obj.update(field=target.newFieldObject(start, end - start))
            entry.append(obj)
            args['sect'] = sect

    return ret


def readImportTable(entry, source, target, args):
    rv = 0
    if 'dirm' in args and 1 in args['dirm']:
        rv, _ = args['dirm'][1]

    def getDllName(nb, dat, **args):
        args['offset'] = offset + args['_i'].atoi(dat, args.get('e', True))
        return target.readString(source, **args)

    _IMPORT = (
        ('Characteristics', 4, 'value'),
        ('TimeDateStamp', 4, 'timestamp'),
        ('ForwarderChain', 4, 'value'),
        ('DLL Name', 4, 'recursive,value', {
            'subdescriptor': (
                ('DLL Name', 4, 'string,export', {'func': getDllName}),
            )}),
        ('First Trunk RVA', 4, 'value,export,objexp')
    )

    ret = rv > 0
    if ret:
        notifyProgress(args, 'Build import table')
        m, na, ofraw, _, vaddr = getPhysicAddress(rv,
                                                  args.get('sect', list()))
        offset = ofraw - vaddr

    start, end = sys.maxint, 0
    obj = target.newItemObject('Import Table')
    while ret:
        args['offset'] = m
        ret, objectp, consumed, _, exported = target.parse(
            'IMPORTS: $0', _IMPORT, afile=source, **args)
        if not ret or exported[1] == 0:
            break

        if objectp.getStart() < start:
            start = objectp.getStart()
        tend = objectp.getStart() + objectp.getSize()
        if tend > end:
            end = tend

        obj.appendChildren(objectp)
        # First Trunk RVA
        k, vft = 1, exported[1] + offset
        while k > 0:
            args['offset'] = vft
            va = target.readValue(source, 4, vft, **args)
            if va == 0:
                k = 0
                break

            of = va + offset
            # order no, name
            no = target.readValue(source, 2, off=of, **args)
            na = target.readString(source, off=of + 2, **args)

            objp = target.newItemObject(
                '#%d' % k, '0x%04x (%s)' % (va, na),
                target.newFieldObject(args['start'] + of, 2 + len(na) + 1))

            objp.appendChildren(
                target.newItemObject(
                    'ordn', no,
                    target.newFieldObject(args['start'] + of, 2)))
            objp.appendChildren(
                target.newItemObject(
                    'nam', na,
                    target.newFieldObject(args['start'] + of + 2,
                                          len(na) + 1)))

            exported[2].appendChildren(objp)
            k += 1
            vft += 4

        m += consumed

    if ret:
        obj.update(field=target.newFieldObject(start, end - start))
        entry.append(obj)

    return ret


def readExportTable(entry, source, target, args):
    rv = 0
    if 'dirm' in args and 0 in args['dirm']:
        rv, _ = args['dirm'][0]

    ret = rv > 0
    if ret:
        notifyProgress(args, 'Build export table')
        m, _, ofraw, _, vaddr = getPhysicAddress(rv,
                                                 args.get('sect', list()))
        offset = ofraw - vaddr

    def getDllName(nb, dat, **args):
        args['offset'] = offset + args['_i'].atoi(dat, args.get('e', True))
        return target.readString(source, **args)

    _EXPORT = (
        ('Characteristics', 4, 'value'),
        ('TimeDateStamp', 4, 'timestamp'),
        ('MajorVersion', 2, 'value'),
        ('MinorVersion', 2, 'value'),
        ('Name RVA', 4, 'value', {
            'subdescriptor': (
                ('DLL Name', 4, 'string', {'func': getDllName}),
            )}),
        ('Base', 4, 'value'),
        ('NumberOfFunctions', 4, 'decimal,export,objexp'),
        ('NumberOfNames', 4, 'decimal,export,objexp'),
        ('AddressOfFunctions', 4, 'value,export'),
        ('AddressOfNames', 4, 'value,export'),
        ('AddressOfOriginal', 4, 'value,export')
    )

    if ret:
        args['offset'] = m
        ret, objectp, _, _, exported = target.parse(
            'Export Table', _EXPORT, afile=source, **args)

    if ret:
        entry.append(objectp)

        # NumberOfFunctions
        if exported[0] is not None and exported[0] > 0:
            for k in xrange(exported[0]):
                off = exported[4] + offset + 4 * k
                rva = target.readValue(source, 4, off, **args)
                exported[1].appendChildren(
                    target.newItemObject(
                        '#%d' % k, '0x%04x' % rva,
                        target.newFieldObject(args['start'] + off, 4)))

        # NumberOfNames
        if exported[2] is not None and exported[2] > 0:
            for k in range(exported[2]):
                off = exported[5] + offset + 4 * k
                rva = target.readValue(source, 4, off, **args)
                nam = target.readString(source, off=offset + rva, **args)
                exported[3].appendChildren(
                    target.newItemObject(
                        '#%d' % k, '0x%x %s' % (rva, nam),
                        target.newFieldObject(args['start'] + off, 4),
                        node=target.newItemObject(
                            'FunctionName', nam,
                            target.newFieldObject(
                                args['start'] + offset + rva,
                                len(nam) + 1))))

    return ret


class ExecuteDumper:
    __aof__ = ('Executable Dumper', 'EXE')

    def Execute(self, source, target, notify, **args):
        # define owned arguments
        args['f'] = source
        args['l'] = len(source)
        args['c'] = notify
        args['offset'] = 0

        entry = target.getEntry()
        # 1. check the magic number
        ret = checkSignature(source, target, (0x5a4d, 'MZ'), args)
        # 2. read DOS header
        if ret:
            ret = readDosHeaderInfo(entry, source, target, args)
        # 3. read File header
        if ret:
            ret = readPeCoffHeader(entry, source, target, args)
        # 4. read section table
        if ret:
            ret = readSectionTable(entry, source, target, args)
        # 5. read import table
        if ret:
            ret = readImportTable(entry, source, target, args)
        # 6. read export table
        if ret:
            ret = readExportTable(entry, source, target, args)

        return ret


class ObjectDumper:
    __aof__ = ('Object Dumpers', 'OBJ')

    def Execute(self, source, target, notify, **args):
        # define owned arguments
        args['f'] = source
        args['l'] = len(source)
        args['c'] = notify
        args['offset'] = 0

        entry = target.getEntry()
        # 1. check the magic number - only 'Intel 386' supported
        ret = checkSignature(source, target, (0x014c, '\x01\x4c'), args)
        # 2. read File header
        if ret:
            ret = readPeCoffHeader(entry, source, target, args)
        # 3. read section table
        if ret:
            ret = readSectionTable(entry, source, target, args)
        # 4. read import table
        if ret:
            ret = readImportTable(entry, source, target, args)
        # 5. read export table
        if ret:
            ret = readExportTable(entry, source, target, args)

        return ret


def getElfClass(nb, dat, **args):
    va = 'Invalid Class'
    cl = ord(dat[0])
    if (cl == 0x01):
        va = '32-bit object'
    elif (cl == 0x02):
        va = '64-bit object'

    return '%d (%s)' % (cl, va)


def getOsAbiName(nb, dat, **args):
    _ABI = {
        0x00: 'Unix - System V', 0x01: 'HP-UX',
        0x02: 'NetBSD',          0x03: 'Linux',
        0x04: 'Hurd',            0x05: '86Open common IA32',
        0x06: 'Solaris',         0x07: 'Monterey',
        0x08: 'IRIX',            0x09: 'FreeBSD',
        0x0a: 'TRU64 UNIX',      0x0b: 'Modesto',
        0x0c: 'OpenBSD',         0x61: 'ARM',
        0xff: 'Standalone'
    }

    abi = ord(dat[0])
    return '0x%02x (%s)' % (abi, _ABI.get(abi, 'Unknown'))


def getElfIdentData(nb, dat, **args):
    va = ord(dat)
    if va == 0x02:
        tp = 'big-endian'
    else:
        tp = 'little-endian'

    return '%d (%s)' % (va, tp)


def getElfType(nb, dat, **args):
    tp = 'Invalid Value'
    _ELF_TYPE = {
        0: 'No file type',
        1: 'Relocatable file',
        2: 'Executable file',
        3: 'Shared object file',
        4: 'core file',
        0xff00: 'Processor-specific',
        0x00ff: 'Processor-specific'
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    if va in _ELF_TYPE:
        tp = _ELF_TYPE[va]

    return '0x%x (%s)' % (va, tp)


def getElfSectionType(nb, dat, **args):
    tp = 'unknown'
    _SECTION_TYPE = {
        0: 'SHT_NULL',    1: 'SHT_PROGBITS',
        2: 'SHT_SYMTAB',  3: 'SHT_STRTAB',
        4: 'SHT_RELA',    5: 'SHT_HASH',
        6: 'SHT_DYNAMIC', 7: 'SHT_NOTE',
        8: 'SHT_NOBITS',  9: 'SHT_REL',
        10: 'SHT_SHLIB',  11: 'SHT_DYNSYM',
        14: 'SHT_INIT_ARRAY',
        15: 'SHT_FINI_ARRAY',
        16: 'SHT_PREINIT_ARRAY',
        17: 'SHT_GROUP',  18: 'SHT_SYMTAB_SHND',
        0x60000000: 'SHT_LOOS',
        0x6ffffff0: 'SHT_VERSYN',
        0x6ffffffc: 'SHT_VERDEF',
        0x6ffffffd: 'SHT_SUNW_verdef',
        0x6ffffffe: 'SHT_SUNW_verneed',
        0x6fffffff: 'SHT_HIOS',
        0x70000000: 'SHT_LOPROC',
        0x7fffffff: 'SHT_HIPROC',
        0x80000000: 'SHT_LOUSER',
        0xffffffff: 'SHT_HIUSER'
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    if va in _SECTION_TYPE:
        tp = _SECTION_TYPE[va]

    return '0x%x (%s)' % (va, tp)


def getElfMachine(nb, dat, **args):
    tp = 'unknown'
    # reserved value
    # The value refers to the website:
    # http://www.sco.com/developers/gabi/2000-07-17/ch4.eheader.html

    rv = ((6, 6), (11, 14), (16, 16), (22, 35), (62, 65))

    _ELF_MACHINE = {
        0:  'No machine',                1:  'AT&T WE 32100',
        2:  'SPARC',                     3:  'Intel 80386',
        4:  'Motorola 68000',            5:  'Motorola 88000',
        7:  'Intel 80860',               8:  'MIPS RS3000',
        9:  'IBM System/370 Processor',  10: 'MIPS RS3000 little-endian',
        15: 'HP PA-RISC',                17: 'Fujitsu VPP500',
        18: 'Enhanced instruction set SPARC',
        19: 'Intel 80960',               20: 'Power PC',
        21: '64-bit Power PC',           36: 'NEC V800',
        37: 'Fujitsu FR20',              38: 'TRW RH-32',
        39: 'Motorola RCE',              40: 'Advanced RISC Machine ARM',
        41: 'Digital Alpha',             42: 'Hitachi SH',
        43: 'SPARC Version 9',
        44: 'Siemens Tricore embedded processor',
        45: 'Argonaut RISC Core',        46: 'Hitachi H8/300',
        47: 'Hitachi H8/300H',           48: 'Hitachi H8S',
        49: 'Hitachi H8/500',
        50: 'Intel IA-64 processor architecture',
        51: 'Stanford MIPS-X',           52: 'Motorola ColdFire',
        53: 'Motorola M68HC12',
        54: 'Fujitsu MMA Multimedia Accelerator',
        55: 'Siemens PCP',
        56: 'Sony nCPU embedded RISC processor',
        57: 'Denso NDR1 microprocessor', 58: 'Motorola Star*Core processor',
        59: 'Toyota ME16 processor',     60: 'ST100 processor',
        61: 'TinyJ embedded processor',  66: 'Siemens FX66 microcontroller',
        67: 'ST9+ 8/16 bit microcontroller',
        68: 'ST7 8-bit microcontroller', 69: 'Motorola MC68HC16',
        70: 'Motorola MC68HC11',         71: 'Motorola MC68HC08',
        72: 'Motorola MC68HC05',         73: 'Silicon Graphics SVx',
        74: 'ST19 8-bit microcontroller',
        75: 'Digital VAX',
        76: 'Axis Commnications 32-bit embedded processor',
        77: 'Infineon 32-bit embedded processor',
        78: 'Element 16 64-bit DSP',     79: 'LSI Logic 16-bit DSP',
        80: "Donald Knuth's educational 64-bit processor",
        81: 'Harvard University machine-independent object files',
        82: 'SiTera Prism'
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    if va in _ELF_MACHINE:
        tp = _ELF_MACHINE[va]
    else:
        for v in rv:
            s, e = v
            if s <= va <= e:
                tp = 'Reserved for future use'
                break

    return '%d (%s)' % (va, tp)


def getElfSectionFlag(nb, dat, **args):
    tx = ''
    ma = 0
    tp = 'unknown'
    _SECTION_FLAG = {
        0x1:   'W',  # SHF_WRITE
        0x2:   'A',  # SHF_ALLOC
        0x4:   'X',  # SHF_EXECINSTR
        0x10:  'M',  # SHF_MERGE
        0x20:  'S',  # SHF_STRING
        0x40:  'I',  # SHF_INFO_LINK
        0x80:  'L',  # SHF_LINK_ORDER
        0x100: 'O',  # SHF_OS_NONCONFORMING
        0x200: 'G',  # SHF_GROUP
        0x400: 'T'   # SHF_TLS
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    tb = _SECTION_FLAG.keys()
    tb.sort()
    for k in tb:
        if va & k:
            tx += _SECTION_FLAG[k]
        ma |= k

    if va & 0xff000000:
        tx += 'o'
    elif va & 0xf0000000:
        tx += 'p'
    elif (va & ma) == 0:
        tx += 'x'

    if len(tx):
        tp = tx
    return '0x%x (%s)' % (va, tp)


def getElfSymbolInfo(nb, dat, **args):
    va = ord(dat)
    ta = []

    _STB = {
        0: 'STB_LOCAL',  1: 'STB_GLOBAL',
        2: 'STB_WEAK',   13: 'STB_LOPROC',
        15: 'STB_HIPROC'
    }

    _STT = {
        0: 'STT_NOTYPE', 1: 'STT_OBJECT',
        2: 'STT_FUNC',   3: 'STT_SECTION',
        4: 'STT_FILE',   13: 'STT_LOPROC',
        15: 'STT_HIPROC'
    }

    if (va >> 16) in _STB.has_key:
        ta.append(_STB[va >> 16])

    if (va & 0xffff) in _STT:
        ta.append(_STT[va & 0xffff])

    return '%d (%s)' % (va, ', '.join(ta))


def getElfSegmentType(nb, dat, **args):
    tp = 'unknown'
    _SEGMENT_TYPE = {
        0: 'PT_NULL',     1: 'PT_LOAD',
        2: 'PT_DYNAMIC',  3: 'PT_INTERP',
        4: 'PT_NOTE',     5: 'PT_SHLIB',
        6: 'PT_PHDR',     7: 'PT_TLS',
        0x60000000: 'PT_LOOS',
        0x6fffffff: 'PT_HIOS',
        0x70000000: 'PT_LOPROC',
        0x7fffffff: 'PT_HIPROC'
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    if va in _SEGMENT_TYPE:
        tp = _SEGMENT_TYPE[va]

    return '0x%04x (%s)' % (va, tp)


def getElfSegmentFlag(nb, dat, **args):
    tp = ''
    va = args['_i'].atoi(dat, args.get('e', True))

    if va & 0x01:
        tp += 'X'
    if va & 0x02:
        tp += 'R'
    if va & 0x04:
        tp += 'W'

    if len(tp):
        return '0x%x (%s)' % (va, tp)
    else:
        return '0x%x' % va


def getElfDynamicTag(nb, dat, **args):
    _DYNAMIC_TYPE = {
        0: 'DT_NULL',     1: 'DT_NEEDED',
        2: 'DT_PLTRELSZ', 3: 'DT_PLTGOT',
        4: 'DT_HASH',     5: 'DT_STRTAB',
        6: 'DT_SYMTAB',   7: 'DT_SYMTAB',
        8: 'DT_RELASZ',   9: 'DT_RELAENT',
        10: 'DT_STRSZ',    11: 'DT_SYMENT',
        12: 'DT_INIT',     13: 'DT_FINI',
        14: 'DT_SONAME',   15: 'DT_RPATH',
        16: 'DT_SYMBOLIC', 17: 'DT_REL',
        18: 'DT_RELSZ',    19: 'DT_RELENT',
        20: 'DT_PLTREL',   21: 'DT_DEBUG',
        22: 'DT_TEXTREL',  23: 'DT_JMPREL',
        0x70000000: 'DT_LOPROC',
        0x7fffffff: 'DT_HIPROC'
    }

    va = args['_i'].atoi(dat, args.get('e', True))
    if va in _DYNAMIC_TYPE:
        tp = _DYNAMIC_TYPE[va]
        return '0x%x (%s)' % (va, tp)
    else:
        return '0x%x' % va


def createElfSymbolTableFormatContext(entry, source, target,
                                      offset, size, args):
    # http://docs.freebsd.org/info/stabs/stabs.info.Symbol_Table_Format.html
    return True


def createElfInterpContext(entry, source, target, offset, size, args):
    interp = target.readString(source, size, off=offset, **args)
    return entry.appendChildren(
        target.newItemObject(
            'Interpreter', interp,
            target.newFieldObject(args['start'] + offset, size)))


def createElfDynamicTableContext(entry, source, target, offset, size, args):
    _DYNAMIC_TABLE = (
        ('', 8, 'recursive', {
            'repeat': size / 8,
            'subdescriptor': (
                ('d_tag', 4, 'string', {'func': getElfDynamicTag}),
                ('d_un', 4, 'value')
            )}),
    )

    ret, objectp, _, _, _ = target.parse('', _DYNAMIC_TABLE,
                                         afile=source, **args)
    if ret:
        entry.appendChildren(objectp.getChildren())

    return ret


def createElfSymbolTableContext(entry, source, target, offset, size, args):
    def getElfName(nb, dat, **args):
        # length is up to 4
        dat = dat[:4]
        offset = args['_i'].atoi(dat, args.get('e', True))
        name = target.readString(args['f'],
                                 off=args.get('shstrtbl', 0) + offset, **args)

        return name

    x64 = args.get('x64', False)
    if x64:
        _SYMBOL_TABLE = (
            ('', 24, 'string', {
                'func': getElfName,
                'repeat': size / 24,
                'subdescriptor': (
                    ('st_name', 4, 'string', {'func': getElfName}),
                    ('st_info', 1, 'string', {'func': getElfSymbolInfo}),
                    ('st_other', 1, 'ignore'),
                    ('st_shndx', 2, 'value')
                    ('st_value', 8, 'value'),
                    ('st_size', 8, 'decimal')
                )}),
        )
    else:
        _SYMBOL_TABLE = (
            ('', 16, 'string', {
                'func': getElfName,
                'repeat': size / 16,
                'subdescriptor': (
                    ('st_name', 4, 'string', {'func': getElfName}),
                    ('st_value', 4, 'value'),
                    ('st_size', 4, 'decimal'),
                    ('st_info', 1, 'string', {'func': getElfSymbolInfo}),
                    ('st_other', 1, 'value'),
                    ('st_shndx', 2, 'value')
                )}),
        )

    ret, objectp, _, _, _ = target.parse('', _SYMBOL_TABLE,
                                         afile=source, **args)
    if ret:
        entry.appendChildren(objectp.getChildren())

    return ret


def createElfStringTableContext(entry, source, target, offset, size, args):
    k = 0
    while k < size:
        symbol = target.readString(source, off=offset + k, **args)
        length = len(symbol) + 1
        entry.appendChildren(
            target.newItemObject(
                '#%d' % k, symbol,
                target.newFieldObject(args['start'] + offset + k, length)))
        k += length

    return True


def createElfCommentContext(entry, source, target, offset,
                            size, args, prefix='Comment '):
    k, cm = 0, list()
    while k < size:
        comment = target.readString(source, -1, offset + k, **args)
        # if we find two continuous zeros, stop the analysis
        if len(comment) == 0:
            break
        else:
            cm.append((k, comment))
            k += len(comment) + 1

    for k, (o, c) in enumerate(cm):
        entry.appendChildren(
            target.newItemObject(
                '%s#%d' % (prefix, k), c,
                target.newFieldObject(args['start'] + offset + o, len(c) + 1)))

    return True


def createElfNoteContext(entry, source, target, offset, size, args):
    k = o = 0
    while k < size:
        obj = target.newItemObject('#%d' % o)
        ofz = offset + k
        namesz = target.readValue(source, 4, ofz, **args)
        obj.appendChildren(
            target.newItemObject(
                'namesz', namesz,
                target.newFieldObject(args['start'] + ofz, 4)))
        ofz += 4
        descsz = target.readValue(source, 4, ofz, **args)
        obj.appendChildren(
            target.newItemObject(
                'descsz', descsz,
                target.newFieldObject(args['start'] + ofz, 4)))
        ofz += 4
        tp = target.readValue(source, 4, ofz, **args)
        obj.appendChildren(
            target.newItemObject(
                'type', tp,
                target.newFieldObject(args['start'] + ofz, 4)))
        ofz += 4
        name = target.readString(source, namesz, off=ofz, **args)
        obj.appendChildren(
            target.newItemObject(
                'name', name,
                target.newFieldObject(args['start'] + ofz, namesz)))
        ofz += (namesz + 3) & (~0x03)
        if descsz > 0:
            desc = target.readString(source, descsz, off=ofz, **args)
            obj.appendChildren(
                target.newItemObject(
                    'desc', desc,
                    target.newFieldObject(args['start'] + ofz, descsz)))
            ofz += (descsz + 3) & (~0x03)

        obj.update(content=name, field=target.newFieldObject(k, ofz))
        entry.appendChildren(obj)

        k += ofz
        o += 1

    return True


def readElfHeader(entry, source, target, args):
    _MAGIC = (
        ('Magic', 4, 'string,verify', {'verified': '\x7fELF'}),
        ('Class', 1, 'string,export', {'func': getElfClass}),  # 0
        ('Data', 1, 'string,export',
         {'func': getElfIdentData}),  # 1
        ('Version', 1, 'decimal'),
        ('OS/ABI', 1, 'string', {'func': getOsAbiName}),
        ('ABI Version', 1, 'decimal'),
        ('EI_PAD', 5, 'ignore'),
        ('NIDENT', 2, 'decimal,ignore')
    )

    x64 = False
    notifyProgress(args, 'Read ELF header')

    ret, objectp, consumed, _, exported = target.parse(
        'ELF Header', _MAGIC, afile=source, **args)
    if ret:
        # validate the endian and re-read the data again
        endian = exported[1].lower().find('little-endian') != -1
        # re-read the header again
        args['e'] = endian
        target.setEndian(endian, args)
        x64 = exported[0].find('64') > -1

    _ELF_HEADER = (
        ('Ident', 16, 'string,recursive', {'subdescriptor': _MAGIC}),
        ('e_type', 2, 'string', {'func': getElfType}),
        ('e_machine', 2, 'string', {'func': getElfMachine}),
        ('e_version', 4, 'decimal'),
        ('e_entry', 8 if x64 else 4, 'svalue'),
        ('e_phoff', 8 if x64 else 4, 'svalue,export'),  # 2
        ('e_shoff', 8 if x64 else 4, 'svalue,export'),  # 3
        ('e_flags', 4, 'svalue'),
        ('e_ehsize', 2, 'decimal'),
        ('e_phentsize', 2, 'decimal'),
        ('e_phnum', 2, 'decimal,export'),  # 4
        ('e_shentsize', 2, 'decimal'),
        ('e_shnum', 2, 'decimal,export'),  # 5
        ('e_shstrndx', 2, 'decimal,export')  # 6
    )

    if ret:
        ret, objectp, consumed, _, exported = target.parse(
            'ELF Header', _ELF_HEADER, afile=source, **args)

    if ret:
        args['x64'] = x64
        args['ofph'] = exported[2]
        args['ofsh'] = exported[3]
        args['nbph'] = exported[4]
        args['nbsh'] = exported[5]
        args['strndx'] = exported[6]
        args['offset'] += consumed

        entry.append(objectp)

    return ret


def handleElfSection(entry, source, target, args):
    ret = True

    nbsh = args.get('nbsh', 0)
    if nbsh > 0:
        x64 = args.get('x64', False)
        itsize = 64 if x64 else 40

        def getElfName(nb, dat, **args):
            # length is up to 4
            dat = dat[:4]
            offset = args['_i'].atoi(dat, args.get('e', True))
            name = target.readString(
                args['f'], off=args.get('shstrtbl', 0) + offset, **args)

            return name

        def getSectionHeader(num):
            return (
                ('Section', itsize, 'recursive,string', {
                    'repeat': num,
                    'func': getElfName,
                    'subdescriptor': (
                        ('sh_name', 4, 'string,export', {'func': getElfName}),
                        ('sh_type', 4, 'string,export',
                         {'func': getElfSectionType}),
                        ('sh_flags', 8 if x64 else 4, 'string',
                         {'func': getElfSectionFlag}),
                        ('sh_addr', 8 if x64 else 4, 'svalue'),
                        ('sh_offset', 8 if x64 else 4, 'svalue,export'),
                        ('sh_size', 8 if x64 else 4, 'decimal,export'),
                        ('sh_link', 4, 'svalue'),
                        ('sh_info', 4, 'svalue'),
                        ('sh_addralign', 8 if x64 else 4, 'svalue'),
                        ('sh_entsize', 8 if x64 else 4, 'svalue')
                    )}),
            )

        # handle the section mentioned strndx first to satisfy section names
        args['offset'] = args['ofsh'] + args['strndx'] * itsize
        ret, objectp, _, _, exported = target.parse(
            '', getSectionHeader(1), afile=source, **args)
        if ret:
            args['offset'] = args['ofsh']
            args['shstrtbl'] = exported[2]
            ret, objectp, _, _, exported = target.parse(
                'Section Header', getSectionHeader(nbsh),
                afile=source, **args)

            if ret:
                entry.append(objectp)

        if ret:
            obj = target.newItemObject('Section')
            for k in range(len(exported) / 4):
                na, tp, of, size = exported[4 * k:4 * k + 4]
                objp = target.newItemObject(
                    'Section #%d' % k, na,
                    target.newFieldObject(args['start'] + of, size))
                if na == '.interp':
                    ret = createElfInterpContext(objp, source, target,
                                                 of, size, args)
                elif na == '.comment':
                    ret = createElfCommentContext(objp, source, target,
                                                  of, size, args)
                elif na == '.stab':
                    ret = createElfSymbolTableFormatContext(objp, source,
                                                            target, of, size,
                                                            args)
                elif na == '.debug_str':
                    ret = createElfCommentContext(objp, source, target,
                                                  of, size, args, '')
                elif tp.find('SHT_SYMTAB') > -1:
                    ret = createElfSymbolTableContext(objp, source, target,
                                                      of, size, args)
                elif tp.find('SHT_STRTAB') > -1:
                    ret = createElfStringTableContext(objp, source, target,
                                                      of, size, args)
                elif tp.find('SHT_DYNAMIC') > -1:
                    ret = createElfDynamicTableContext(objp, source, target,
                                                       of, size, args)
                elif tp.find('SHT_NOTE') > -1:
                    ret = createElfNoteContext(objp, source, target,
                                               of, size, args)

                obj.appendChildren(objp)

            entry.append(obj)

    return ret


def handleProgramSegments(entry, source, target, args):
    ret = True

    nbph = args.get('nbph', 0)
    if nbph > 0:
        x64 = args.get('x64', False)
        if x64:
            _PROGRAM_HEADER = (
                ('Header', 56, 'recursive', {
                    'repeat': nbph,
                    'subdescriptor': (
                        ('p_type', 4, 'string', {'func': getElfSegmentType}),
                        ('p_flags', 4, 'string', {'func': getElfSegmentFlag}),
                        ('p_offset', 8, 'svalue'),
                        ('p_vaddr', 8, 'svalue'),
                        ('p_paddr', 8, 'svalue,export'),
                        ('p_filesz', 8, 'svalue,export'),
                        ('p_memsz', 8, 'svalue'),
                        ('p_align', 8, 'decimal')
                    )}),
            )
        else:
            _PROGRAM_HEADER = (
                ('Header', 32, 'recursive', {
                    'repeat': nbph,
                    'subdescriptor': (
                        ('p_type', 4, 'string', {'func': getElfSegmentType}),
                        ('p_offset', 4, 'svalue'),
                        ('p_vaddr', 4, 'svalue'),
                        ('p_paddr', 4, 'svalue,export'),
                        ('p_filesz', 4, 'svalue,export'),
                        ('p_memsz', 4, 'svalue'),
                        ('p_flags', 4, 'string', {'func': getElfSegmentFlag}),
                        ('p_align', 4, 'decimal')
                    )}),
            )

        args['offset'] = args['ofph']
        ret, objectp, _, _, exported = target.parse(
            'Program Header Table', _PROGRAM_HEADER, afile=source, **args)

        if ret:
            entry.append(objectp)

            obj = target.newItemObject('Segment')
            for k in range(len(exported) / 2):
                of, size = exported[2 * k:2 * k + 2]
                obj.appendChildren(
                    target.newItemObject(
                        'Segment #%d' % (k + 1),
                        attribute='no_extended',
                        field=target.newFieldObject(args['start'] + of, size)))

            entry.append(obj)

    return ret


class SharedObjectDumper:
    __aof__ = ('Shared Object Dumpers', 'SOD')

    def Execute(self, source, target, notify, **args):
        # define owned arguments
        args['f'] = source
        args['l'] = len(source)
        args['c'] = notify
        args['e'] = True
        args['offset'] = 0

        entry = target.getEntry()
        # 1. check the magic number
        ret = checkSignature(source, target, (0x464c457f, '\x7fELF'), args)
        # 2. read ELF header
        if ret:
            ret = readElfHeader(entry, source, target, args)
        # 3. handle ELF section
        if ret:
            ret = handleElfSection(entry, source, target, args)
        # 4. handle program segments
        if ret:
            ret = handleProgramSegments(entry, source, target, args)

        return ret


def fmtService():
    return ExecuteDumper, ObjectDumper, SharedObjectDumper
